/**
 * Copyright (c) 2009, Robert Schuster
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 *
 * - Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer in the documentation and/or other
 *   materials provided with the distribution.
 * - Neither the name of Robert Schuster nor the names of his
 *   contributors may be used to endorse or promote products
 *   derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This class the the Java equivalent to the C++ Executor Object
 */

package jclcalc;

import java.util.HashMap;

/**
 *
 * @author robert schuster
 */
public class Executor {

    int ExecutorID = -1;

    /**
     * A map with the Arrays and the DataIDs
     */
    HashMap<Object,Integer> Memorys;

    /**
     * Create a new Executor object with one device
     * @param device_typ
     */
    public Executor(int device_typ) {
        //create the C++-Object and store it's id
        ExecutorID = JCLcalc.CLcalc_Executor_new(device_typ);
        //create the Momory object map
        Memorys = new HashMap<Object,Integer>();
    }

    /**
     * Create a new Program-Object form source.
     * @param source        the Source of the program
     *                      The declaration of the kernel must be in the form:
     *                      <p>__kernel void <i>name</i> (__global float* <i>arg1</i>, __global float* <i>arg2</i>, const unsigned int <i>count</i>)</p>
     * @param name          the name of the kernel inside the Program
     * @return              a progID witch refers to the kernel, not to the compiled program.
     * @throws Error
     */
    public int addProgramFromSource(String source, String name) throws Error {
        return JCLcalc.CLcalc_Executor_addProgFromSource(ExecutorID, source, name);
    }

    /**
     * Add an data to a Device
     * @param data          the 1-dim data array of primitive type
     * @param data_dir      the memory typ of the 2nd argument: CL_MEM_*
     * @throws Error
     */
    public void addData(Object data, long data_dir) throws Error {
        int dataid = JCLcalc.CLcalc_Executor_addData(ExecutorID, data, data_dir);
        Memorys.put(data, dataid);
    }

    /**
     * Read Data from a device
     * You only need that function if you don't use CL_MEM_USE_HOST_PTR in addData...
     * The float[]-Object must be the same as the one used to call addData...
     * @param data          the 1-dim data array of primitive type
     * @throws Error
     */
    public void getData(Object data) throws Error {
        Integer DataID = Memorys.get(data);
        if (DataID == null) throw new Error(-1);
        JCLcalc.CLcalc_Executor_getData(ExecutorID, DataID, data);
    }

    /**
     * release a used Momory-Object, that will cause the device to free the Memory
     * The Executor will call that function also inside of the shutdown function
     * @param data          the data array to release
     * @throws Error        in case of any error
     */
    public void releaseData(Object data) throws Error {
        Integer DataID = Memorys.get(data);
        if (DataID == null) throw new Error(-1);
        JCLcalc.CLcalc_Executor_releaseData(ExecutorID, DataID, data);
        Memorys.remove(data);
    }

    /**
     * Assign Data to a Program as an argument
     * @param progID        the ID of the Programm returned by addProgFrom...
     * @param DataObject    the array that will be the argument
     * @param arg_pos       the position of the argument (1st, 2nd, ...)
     * @throws Error        in the case of any Error
     */
    public void setArgument(int progID, int arg_pos, Object DataObject) throws Error {
        Integer DataID = Memorys.get(DataObject);
        if (DataID == null) throw new Error(-1);
        JCLcalc.CLcalc_Executor_setArgument(ExecutorID, progID, arg_pos, DataID);
    }

    /**
     * Execute the Kernel with the Index 0 to MaxIndex
     * @param DeviceID      the ID of the device used while creating the Executor
     * @param progID        the ID of the Programm returned by addProgFrom...
     * @param MaxIndex      the number of kernels to execute. Normaly this is the
     *                      number of elements in your input-array
     * @throws Error
     */
    public void Execute(int DeviceID, int progID, int MaxIndex) throws Error {
        JCLcalc.CLcalc_Executor_Execute(ExecutorID, progID, MaxIndex);
    }

    @Override
    protected void finalize() throws Throwable {
        JCLcalc.CLcalc_Executor_shutdown(ExecutorID);
        super.finalize();
    }

    /**
     * Use one of the OpenCL intrinsic functions which have one argument
     * for example output[i] = sqrt(input[i])
     * @param input         the input array
     * @param output        the output array
     * @param fname         the function to use, example: "sqrt"
     * @throws Error
     */
    public void BuildIn_CLintrinsics1arg(Object input, Object output, String fname) throws Error {
        JCLcalc.CLcalc_Executor_BuildIn_CLintrinsics1arg(ExecutorID, input, output, fname);
    }

    /**
     * Use one of the OpenCL intrinsic functions which have one argument
     * for example output[i] = sqrt(input[i])
     * @param input         the input array
     * @param output        the output array
     * @param fname         the function to use, example: "sqrt"
     * @throws Error
     */
    public void BuildIn_CLintrinsics1arg_execOnly(Object input, Object output, String fname) throws Error {
        Integer inputID = Memorys.get(input);
        if (inputID == null) throw new Error(-1);
        Integer outputID = Memorys.get(output);
        if (outputID == null) throw new Error(-1);
        JCLcalc.CLcalc_Executor_BuildIn_CLintrinsics1arg_execOnly(ExecutorID, inputID, input, outputID, output, fname);
    }

    /**
     * Calculate the sum of each element of 2 arrays
     * @param arg1          the 1st argument array
     * @param arg2          the 2nd argument array
     * @param result        the result array
     * @param fname         the operant, example: "+"
     * @throws Error
     */
    public void BuildIn_basic(Object arg1, Object arg2, Object result, String fname) throws Error {
        JCLcalc.CLcalc_Executor_BuildIn_basic(ExecutorID, arg1, arg2, result, fname);
    }

    /**
     * Calculate the sum of each element of 2 arrays, no data tranfer is
     * will be done. The data must already be on the device.
     * @param arg1          the argument 1 array (only used to get the datatype)
     * @param arg2          the argument 2 array (only used to get the datatype)
     * @param result        the result array (only used to get the datatype)
     * @param fname         the operant, example: "+"
     * @throws Error
     */
    public void BuildIn_basic_execOnly(Object arg1, Object arg2, Object result, String fname) throws Error {
        Integer arg1ID = Memorys.get(arg1);
        if (arg1ID == null) throw new Error(-1);
        Integer arg2ID = Memorys.get(arg2);
        if (arg2ID == null) throw new Error(-1);
        Integer resultID = Memorys.get(result);
        if (arg1ID == null) throw new Error(-1);
        JCLcalc.CLcalc_Executor_BuildIn_basic_execOnly(ExecutorID, arg1ID, arg1, arg2ID, arg2, resultID, result, fname);
    }

}
